﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.IO;
using System.Linq;
using NDBGen.Configuration;

namespace NDBGen
{
    public class Parser
    {
        public static readonly String[] TAB_STRINGS = new String[] { "\t", "    " };

        public Parser()
        {
            this.ScriptedData = new List<ScriptedTableData>();

            this.Tables = new List<Table>();
        }

        private List<ScriptedTableData> ScriptedData { get; set; }
        private List<Table> Tables { get; set; }

        public ParsedInput Parse(InputElement config)
        {
            var data = (
                from sourceGroup in config.Data
                from source in sourceGroup
                let entries = ParseScriptedData(FindSourceFile(source.Path))
                from entry in entries
                group entry by sourceGroup.Name).ToArray();

            var tables = (
                from source in config.Schema
                let entries = Parser.ParseTables(FindSourceFile(source.Path), data)
                from entry in entries
                select entry).ToArray();

            return new ParsedInput(tables, data);
        }

        public static FileInfo FindSourceFile(String path)
        {
            var exeDir = new DirectoryInfo(Environment.CurrentDirectory);
            DirectoryInfo sourceDir;
            if (exeDir.Parent.Name == "bin") sourceDir = exeDir.Parent.Parent;
            else sourceDir = exeDir;

            return new FileInfo(Path.Combine(sourceDir.FullName, path));
        }

        internal static List<ScriptedTableData> ParseScriptedData(FileInfo input)
        {
            var content = File.ReadAllText(input.FullName);
            return ParseScriptedData(input.FullName, content);
        }

        internal static List<ScriptedTableData> ParseScriptedData(String path, String s)
        {
            var scriptedData = new List<ScriptedTableData>();

            var lines = new Queue<String>(
                from line in s.Split(new String[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
                where line.Trim() != String.Empty
                    && !line.StartsWith("-")
                select line);

            var defs = new List<ScriptedTableDataDefinition>();
            ScriptedTableDataDefinition item = null;
            while (lines.Any())
            {
                var line = lines.Dequeue();

                if (line.StartsWith("#"))
                {
                    if (item != null) defs.Add(item);
                    item = new ScriptedTableDataDefinition(path) { TableDefLine = line, DataLines = new List<String>() };
                    continue;
                }
                else if (line.StartsWith("|"))
                {
                    item.ColumnDefLine = line;
                    continue;
                }
                else
                {
                    item.DataLines.Add(line);
                    if (!lines.Any()) defs.Add(item);
                }
            }

            var missingColumnDefLine = defs.FirstOrDefault(d => String.IsNullOrEmpty(d.ColumnDefLine));
            if (missingColumnDefLine != null) throw new ArgumentException(String.Format("{0} is missing its column definition line", missingColumnDefLine.TableDefLine));

            var result = defs.Select(d => ScriptedTableData.Parse(d)).ToList();
            scriptedData.AddRange(result);

            return scriptedData;
        }

        internal static List<Table> ParseTables(String s)
        {
            return ParseTables(s, null);
        }

        internal static List<Table> ParseTables(FileInfo input, IGrouping<String, ScriptedTableData>[] scriptedData)
        {
            var content = File.ReadAllText(input.FullName);
            return Parser.ParseTables(content, scriptedData);
        }

        internal static List<Table> ParseTables(String content, IGrouping<String, ScriptedTableData>[] scriptedData)
        {
            var tables = new List<Table>();
            var schemas = content.Chop();

            // Parse each schemaData individually
            foreach (var schemaData in schemas)
            {
                // first line is the schema name.
                var lines = schemaData.GetLines();
                var schemaName = lines.First();
                lines.RemoveAt(0);
                var tbls = lines.CombineLines().Chop();
                foreach (var line in tbls)
                {
                    var table = new Table(line, schemaName);
                    table.Parse(scriptedData);
                    tables.Add(table);
                }
            }

            return tables;
        }
    }
}